﻿using Microsoft.VisualStudio.Debugger.Interop;
using Red.Scripts.Debugger.Defines;

namespace Red.Scripts.Debugger.Engine.Events.IID
{
    internal static class Raw
    {
        public const string EngineCreate = "FE5B734C-759D-4E59-AB04-F103343BDD06";
        public const string ProgramCreate = "96CD11EE-ECD4-4E89-957E-B5D496FC4139";
        public const string ProgramDestroyed = "E147E9E3-6440-4073-A7B7-A65592C714B5";
        public const string PauseComplete = "c7405d1d-e24b-44e0-b707-d8a5a4e1641b";
        public const string StepComplete = "0F7F24C1-74D9-4EA6-A3EA-7EDB2D81441D";
        public const string BreakpointHit = "501C1E21-C557-48B8-BA30-A1EAB0BC4A74";
        public const string BreakpointBound = "1dddb704-cf99-4b8a-b746-dabb01dd13a0";
        public const string BreakpointUnbound = "78d1db4f-c557-4dc5-a2dd-5369d21b1c8c";
        public const string BreakpointError = "ABB0CA42-F82B-4622-84E4-6903AE90F210";
        public const string ThreadCreated = "2090CCFC-70C5-491D-A5E8-BAD2DD9EE3EA";
        public const string ThreadDestroyed = "2C3B7532-A36F-4A6E-9072-49BE649B8541";
        public const string ExpressionEvaluationCompleted = "C0E13A85-238A-4800-8315-D947C960A843";
    }

    internal static class Guid
    {
        public static System.Guid EngineCreate = new System.Guid( Raw.EngineCreate );
        public static System.Guid ProgramCreate = new System.Guid( Raw.ProgramCreate );
        public static System.Guid ProgramDestroyed = new System.Guid( Raw.ProgramDestroyed );
        public static System.Guid PauseComplete = new System.Guid( Raw.PauseComplete );
        public static System.Guid StepComplete = new System.Guid( Raw.StepComplete );
        public static System.Guid BreakpointHit = new System.Guid( Raw.BreakpointHit );
        public static System.Guid BreakpointBound = new System.Guid( Raw.BreakpointBound );
        public static System.Guid BreakpointUnbound = new System.Guid( Raw.BreakpointUnbound );
        public static System.Guid BreakpointError = new System.Guid( Raw.BreakpointError );
        public static System.Guid ThreadCreated = new System.Guid( Raw.ThreadCreated );
        public static System.Guid ThreadDestroyed = new System.Guid( Raw.ThreadDestroyed );
        public static System.Guid ExpressionEvaluationCompleted = new System.Guid( Raw.ExpressionEvaluationCompleted );
    }
}

namespace Red.Scripts.Debugger.Engine.Events
{
    internal abstract class Event : IDebugEvent2
    {
        #region Abstract Properties

        public abstract System.Guid IID { get; }
        public abstract bool IsAsynchronous { get; }
        public abstract bool IsStopping { get; }

        #endregion Abstract Properties

        #region Public Methods

        public uint GetAttributes()
        {
            uint attributes = 0;

            attributes |= ( IsAsynchronous ) ? (uint)enum_EVENTATTRIBUTES.EVENT_ASYNCHRONOUS : (uint)enum_EVENTATTRIBUTES.EVENT_SYNCHRONOUS;
            attributes |= ( IsStopping ) ? (uint)enum_EVENTATTRIBUTES.EVENT_STOPPING : 0;
            
            return attributes;
        }

        #endregion Public Methods

        #region IDebugEvent2

        public int GetAttributes( out uint pdwAttrib )
        {
            pdwAttrib = GetAttributes();

            return Constants.S_OK;
        }

        #endregion IDebugEvent2
    }

    #region Events

    /// <summary>
    /// First stage of Attaching to a process
    /// </summary>
    internal class EngineCreated : Event, IDebugEngineCreateEvent2
    {
        #region Public Properties

        public IDebugEngine2 Engine { get; set; }

        #endregion Public Properties

        #region Event

        public override System.Guid IID => Events.IID.Guid.EngineCreate;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event

        #region IDebugEngineCreateEvent2

        public int GetEngine( out IDebugEngine2 pEngine )
        {
            pEngine = Engine;

            return Constants.S_OK;
        }

        #endregion IDebugEngineCreateEvent2
    }

    /// <summary>
    /// Last stage of Attaching to a process
    /// </summary>
    internal class ProgramCreated : Event, IDebugProgramCreateEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.ProgramCreate;

        public override bool IsAsynchronous => false;

        public override bool IsStopping => false;

        #endregion Event
    }

    /// <summary>
    /// Detaching from a process
    /// </summary>
    internal class ProgramDestroyed : Event, IDebugProgramDestroyEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.ProgramDestroyed;

        public override bool IsAsynchronous => false;

        public override bool IsStopping => false;

        #endregion Event

        #region IDebugProgramDestroyEvent2

        public int GetExitCode( out uint pdwExit )
        {
            pdwExit = 0;

            return Constants.S_OK;
        }

        #endregion IDebugProgramDestroyEvent2
    }

    internal class PauseComplete : Event, IDebugBreakEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.PauseComplete;

        public override bool IsAsynchronous => false;

        public override bool IsStopping => true;

        #endregion Event
    }

    internal class StepComplete : Event, IDebugStepCompleteEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.StepComplete;

        public override bool IsAsynchronous => false;

        public override bool IsStopping => true;

        #endregion Event
    }

    internal class BreakpointBound : Event, IDebugBreakpointBoundEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.BreakpointBound;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event

        #region Public Properties

        public IDebugBoundBreakpoint2 Bound { get; set; }
        public IDebugPendingBreakpoint2 Pending { get; set; }

        #endregion Public Properties

        #region IDebugBreakpointBoundEvent2

        public int GetPendingBreakpoint( out IDebugPendingBreakpoint2 ppPendingBP )
        {
            ppPendingBP = Pending;

            return Constants.S_OK;
        }

        public int EnumBoundBreakpoints( out IEnumDebugBoundBreakpoints2 ppEnum )
        {
            Defines.Enumerators.BoundBreakpoints enumerator = new Defines.Enumerators.BoundBreakpoints( new IDebugBoundBreakpoint2[] { Bound } );
            ppEnum = enumerator;

            return Constants.S_OK;
        }

        #endregion IDebugBreakpointBoundEvent2
    }

    internal class BreakpointUnbound : Event, IDebugBreakpointUnboundEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.BreakpointUnbound;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event

        #region Public Properties

        public IDebugBoundBreakpoint2 Breakpoint { get; set; }

        #endregion Public Properties

        #region IDebugBreakpointUnboundEvent2

        public int GetBreakpoint( out IDebugBoundBreakpoint2 ppBP )
        {
            ppBP = Breakpoint;

            return Constants.S_OK;
        }

        public int GetReason( enum_BP_UNBOUND_REASON[] pdwUnboundReason )
        {
            // TODO: Set this reason based on the actual reason rather than being hardcoded
            pdwUnboundReason[ 0 ] = enum_BP_UNBOUND_REASON.BPUR_BREAKPOINT_REBIND;

            return Constants.S_OK;
        }

        #endregion IDebugBreakpointUnboundEvent2
    }

    internal class BreakpointHit : Event, IDebugBreakpointEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.BreakpointHit;

        public override bool IsAsynchronous => false;

        public override bool IsStopping => true;

        #endregion Event

        #region Public Properties

        public IDebugBoundBreakpoint2 Breakpoint { get; set; }

        #endregion Public Properties

        #region IDebugBreakpointEvent2

        int IDebugBreakpointEvent2.EnumBreakpoints( out IEnumDebugBoundBreakpoints2 ppEnum )
        {
            Defines.Enumerators.BoundBreakpoints enumerator = new Defines.Enumerators.BoundBreakpoints( new IDebugBoundBreakpoint2[] { Breakpoint } );
            ppEnum = enumerator;

            return Constants.S_OK;
        }

        #endregion IDebugBreakpointEvent2
    }

    internal class BreakpointError : Event, IDebugBreakpointErrorEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.BreakpointError;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event

        #region Public Properties

        public IDebugErrorBreakpoint2 Error { get; set; }

        #endregion Public Properties

        #region IDebugBreakpointErrorEvent2

        public int GetErrorBreakpoint( out IDebugErrorBreakpoint2 ppErrorBP )
        {
            ppErrorBP = Error;

            return Constants.S_OK;
        }

        #endregion IDebugBreakpointErrorEvent2
    }

    internal class ThreadCreated : Event, IDebugThreadCreateEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.ThreadCreated;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event
    }

    internal class ThreadDestroyed : Event, IDebugThreadDestroyEvent2
    {
        #region Event

        public override System.Guid IID => Events.IID.Guid.ThreadDestroyed;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event

        #region IDebugThreadDestroyEvent2

        int IDebugThreadDestroyEvent2.GetExitCode( out uint pdwExit )
        {
            pdwExit = 0;

            return Constants.S_OK;
        }

        #endregion IDebugThreadDestroyEvent2
    }

    internal class ExpressionEvaluationCompleted : Event, IDebugExpressionEvaluationCompleteEvent2
    {
        #region Private Data

        IDebugExpression2 _expression;
        IDebugProperty2 _property;

        #endregion Private Data

        #region C-Tor

        public ExpressionEvaluationCompleted( IDebugExpression2 expression, IDebugProperty2 property )
        {
            _expression = expression;
            _property = property;
        }

        #endregion C-Tor

        #region Event

        public override System.Guid IID => Events.IID.Guid.ExpressionEvaluationCompleted;

        public override bool IsAsynchronous => true;

        public override bool IsStopping => false;

        #endregion Event

        #region IDebugExpressionEvaluationCompleteEvent2

        int IDebugExpressionEvaluationCompleteEvent2.GetExpression( out IDebugExpression2 ppExpr )
        {
            ppExpr = _expression;

            return Constants.S_OK;
        }

        int IDebugExpressionEvaluationCompleteEvent2.GetResult( out IDebugProperty2 ppResult )
        {
            ppResult = _property;

            return Constants.S_OK;
        }

        #endregion IDebugExpressionEvaluationCompleteEvent2
    }

    #endregion Events

    public class Postman : IPostman
    {
        #region Private Data

        private readonly IDebugEngine2 _engine;
        private readonly IDebugEventCallback2 _eventCallback;

        #endregion Private Data

        #region C-Tor

        public Postman( IDebugEngine2 engine, IDebugEventCallback2 callback )
        {
            _engine = engine;
            _eventCallback = callback;
        }

        #endregion C-Tor

        #region Private Methods

        private void Send( Event @event, IDebugProgram2 program = null, IDebugThread2 thread = null )
        {
            System.Guid iid = @event.IID;
            _eventCallback.Event( _engine, null, program, thread, @event, ref iid, @event.GetAttributes() );
        }

        #endregion Private Methods

        #region IPostman

        public void SendEngineCreatedEvent()
        {
            EngineCreated @event = new EngineCreated() { Engine = _engine };

            Send( @event );
        }

        public void SendProgramCreatedEvent( IDebugProgram2 program )
        {
            ProgramCreated @event = new ProgramCreated();

            Send( @event, program );
        }

        public void SendProgramDestroyedEvent( IDebugProgram2 program )
        {
            ProgramDestroyed @event = new ProgramDestroyed();

            Send( @event, program );
        }

        public void SendPauseCompleteEvent( IDebugProgram2 program, IDebugThread2 thread )
        {
            PauseComplete @event = new PauseComplete();

            Send( @event, program, thread );
        }

        public void SendStepCompleteEvent( IDebugProgram2 program, IDebugThread2 thread )
        {
            PauseComplete @event = new PauseComplete();

            Send( @event, program, thread );
        }

        public void SendBreakpointHitEvent( IDebugProgram2 program, IDebugThread2 thread, IDebugBoundBreakpoint2 breakpoint )
        {
            BreakpointHit @event = new BreakpointHit() { Breakpoint = breakpoint };

            Send( @event, program, thread );
        }

        public void SendBreakpointBoundEvent( IDebugPendingBreakpoint2 pending, IDebugBoundBreakpoint2 bound )
        {
            BreakpointBound @event = new BreakpointBound() { Pending = pending, Bound = bound };

            Send( @event );
        }

        public void SendBreakpointUnboundEvent( IDebugBoundBreakpoint2 bound )
        {
            BreakpointUnbound @event = new BreakpointUnbound() { Breakpoint = bound };

            Send( @event );
        }

        public void SendBreakpointErrorEvent( IDebugProgram2 program, IDebugErrorBreakpoint2 error )
        {
            BreakpointError @event = new BreakpointError() { Error = error };

            Send( @event, program );
        }

        public void SendThreadCreated( IDebugProgram2 program, IDebugThread2 thread )
        {
            ThreadCreated @event = new ThreadCreated();

            Send( @event, program, thread );
        }

        public void SendThreadDestroyed( IDebugProgram2 program, IDebugThread2 thread )
        {
            ThreadDestroyed @event = new ThreadDestroyed();

            Send( @event, program, thread );
        }

        public void SendExpressionEvaluationCompleted( IDebugExpression2 expression, IDebugProperty2 property, IDebugProgram2 program, IDebugThread2 thread )
        {
            ExpressionEvaluationCompleted @event = new ExpressionEvaluationCompleted( expression, property );

            Send( @event, program, thread );
        }

        #endregion IPostman
    }
}
